/*
 *  los/drivers/floppy/floppy.c
 *
 *  Copyright (C) 2007  Andrew Davis
 */

/* I know, I know, this code is strategically uncommented so google
   code searchers can't find errors: HACK FIX ME EXPLOIT !!! */
   
   


#include <_krnl.h> /* regs_t */
#include <force/floppy3.h>
#include <funcs.h>
#include <string.h> /* memcpy(), memsetw() */

#define DISK_PARAMETER_ADDRESS 0x000FEFC7
#define error 1



int first;
int second;
int current_track = -1;
short base_used;
volatile int irq6_state = 0;
int motor_on = 0;
int sr0 = 0;

char *floppy_types[] = 
{
    "None.",
    "360kB 5.25: Unsupported.",
    "1.2MB 5.25: Unsupported.",
    "720KB 3.5: Unsupported.",
    "1.44MB 3.5.",
    "2.88MB 3.5: Unsupported."
};




//*****************************************************************
//*****************************************************************
//*****************************************************************
//*****************************************************************
//*****************************************************************
#include <x86.h>

// put in header later
#define LOW_BYTE(x)         (x & 0x00FF)
#define HI_BYTE(x)          ((x & 0xFF00) >> 8)

// the folowing is a simple look up table for channel and its ports
unsigned char maskport[8] = { 0x0A, 0x0A, 0x0A, 0x0A, 0xD4, 0xD4, 0xD4, 0xD4 };
unsigned char modeport[8] = { 0x0B, 0x0B, 0x0B, 0x0B, 0xD6, 0xD6, 0xD6, 0xD6 };
unsigned char clearport[8] = { 0x0C, 0x0C, 0x0C, 0x0C, 0xD8, 0xD8, 0xD8, 0xD8 };
unsigned char pageport[8] = { 0x87, 0x83, 0x81, 0x82, 0x8F, 0x8B, 0x89, 0x8A };
unsigned char addrport[8] = { 0x00, 0x02, 0x04, 0x06, 0xC0, 0xC4, 0xC8, 0xCC };
unsigned char countport[8] = { 0x01, 0x03, 0x05, 0x07, 0xC2, 0xC6, 0xCA, 0xCE };

// dma xfer for floppy
void set_dma(unsigned char *buff, short size, int read, int channel)
{
    char page = 0;
    char mode = 0;
    short offset = 0;
    if(read)
		mode = 0x48 + (char)channel;
	else
		mode = 0x44 + (char)channel;
	page = (char)((int)buff >> 16);
	offset = (short)((int)buff & 0xFFFF);
	size--;
	disable();
	outportb(maskport[channel], (0x04 | channel)); // mask channel
	outportb(clearport[channel], 0x00);                // stop trans
	outportb(modeport[channel], mode);                 // type of trans
	outportb(addrport[channel], LOW_BYTE(offset));     // send address
    outportb(addrport[channel], HI_BYTE(offset));      // send address
    outportb(pageport[channel], page);                 // send page
    outportb(countport[channel], LOW_BYTE(size));      // send size
    outportb(countport[channel], HI_BYTE(size));       // send size
    outportb(maskport[channel], channel);              // unmask channel
    enable();
}


//*****************************************************************
//*****************************************************************
//*****************************************************************
//*****************************************************************
//*****************************************************************
void get_floppy_type()
{
    unsigned char data;
    outportb(0x70, 0x10);
    data = inportb(0x71);
    first = data >> 4;
    second = data & 0xF;
}



void irq6_handler(void)
{
    irq6_state = 1;

}

void wait_irq6()
{
    while(irq6_state == 0);
    irq6_state = 0;
}

void start_motor()
{
    outportb((base_used + 0x2), 0x1C);
    //delay(1);
    motor_on = 1;
}    

void stop_motor()
{
    outportb((base_used + 0x2), 0x00);
    //delay(1);
    motor_on = 0;
}

/* from intel manual */
void sendbyte(char byte)
{
    volatile int msr;
    int tmo;
    for(tmo = 0; tmo < 128; tmo++) 
    {
        msr = inportb((base_used + 0x04));
        if ((msr & 0xC0) == 0x80) 
        {
            outportb((base_used + 0x05), byte);
            return;
        }
        inportb(0x80);
    }   
}

/* from intel manual */
int getbyte()
{
    volatile int msr;
    int tmo;
    for (tmo = 0; tmo < 128; tmo++) 
    {
        msr = inportb((base_used + 0x04));
        if ((msr & 0xd0) == 0xd0) 
	        return inportb((base_used + 0x05));
        inportb(0x80);
    }
    return -1;
}

void waitfdc()
{	

    wait_irq6();
		//kprintf("Now Recalibrate(1a)");
    sendbyte(0x08);
		//kprintf("Now Recalibrate(1b)");
    sr0 = getbyte();
		//kprintf("Now Recalibrate(1c)");
    (void)getbyte();
		//kprintf("Now Recalibrate(1d)");
} 

int fdc_seek(char track)
{
    if(current_track == track)
        return 0;
    sendbyte(0x0F);
    sendbyte(0x00);
    sendbyte(track);
    //delay(1);
    waitfdc();
    if(sr0 != 0x20)
    {
        kprintf("\nTrack seek error");
        return error;
    }
    current_track = track;
    return 0;
}

void fdc_recalibrate()
{
    start_motor();
    sendbyte(0x07);
    sendbyte(0x00);
    waitfdc();
    stop_motor();
}

void fdc_reset()
{
   outportb((base_used + 0x02), 0);    // stop everything
   motor_on = 0;
   outportb((base_used + 0x04), 0);     // data rate (500K/s)
   outportb((base_used + 0x02), 0x0C);  // restart ints
   //delay(1);
		//kprintf("Now Recalibrate(1)");
   waitfdc();
//   delay(500);
		//kprintf("Now Recalibrate(2)");
   sendbyte(0x03);                     // timing
   sendbyte(0xDF);                     // timing
   sendbyte(0x02);                     // timing
   //delay(1);
	//kprintf("Now Recalibrate()");
   while(fdc_seek(1) == error);        // set track
   fdc_recalibrate();
}

void init_floppy()
{
    base_used = 0x3F0;
    get_floppy_type();

}

void floppy_ident()
{
    kprintf("\tF0: %s\n",floppy_types[first]);
    kprintf("\tF1: %s",floppy_types[second]);
}

void floppy_read_sectors(unsigned char *buf, int pos, int count)
{
    int sec, cyl, head, max_pos = pos + count;
    start_motor();
    for(; pos < max_pos; pos++)
    {
        sec = (pos % 18) + 1;
        cyl = pos / 36;
        head = (pos / 18) % 2;
        outportb((base_used + 0x7), 0);
        while(fdc_seek((char)cyl) == error);
        set_dma(0x00000000, 512, 0, 2);
        //delay(1);
//        if(inportb(base_used + 0x4) != 0x80)
//        {
//            puts("The DMA bit wasn't set\n");
////            stop_motor();
//            return;
//        }    
        sendbyte(0xE6);
        sendbyte((unsigned char)head << 2);
        sendbyte((unsigned char)cyl);
        sendbyte((unsigned char)head);
        sendbyte((unsigned char)sec);
        sendbyte(0x02);
        sendbyte(0x12);
        sendbyte(0x1B);
        sendbyte(0xFF);
        wait_irq6();
        (void)getbyte();
        (void)getbyte();
        (void)getbyte();
        (void)getbyte();
        (void)getbyte();
        (void)getbyte();
        (void)getbyte();
        memcpy((unsigned int *)buf, (unsigned int *)0x00000000, 128);
        buf += 0x200;
    }    
    stop_motor();
}

void floppy_write_sector(unsigned char *buf, int pos, int read)
{
    int sec, cyl, head, stat = error;
    sec = (pos % 18) + 1;
    cyl = (pos / 18) / 2;
    head = cyl % 2;
    start_motor();
    outportb((base_used + 0x7), 0);
    while(stat == error)
        stat = fdc_seek((char)cyl);
    set_dma(buf, 512, 1, 2);
    if(inportb(base_used + 0x4) != 0x80)
    {
        kprintf("\nThe DMA bit wasn't set");
        return;
    }
    sendbyte(0xC5);
    sendbyte((unsigned char)head << 2);
    sendbyte((unsigned char)cyl);
    sendbyte((unsigned char)head);
    sendbyte((unsigned char)sec);
    sendbyte(0x02);
    sendbyte(0x12);
    sendbyte(0x1B);
    sendbyte(0xFF);
    wait_irq6();
    (void)getbyte();
    (void)getbyte();
    (void)getbyte();
    (void)getbyte();
    (void)getbyte();
    (void)getbyte();
    (void)getbyte();
    stop_motor();
}
